package com.zkgengkun.support.s3;

import com.amazonaws.AmazonServiceException;
import com.amazonaws.auth.policy.Policy;
import com.amazonaws.auth.policy.Principal;
import com.amazonaws.auth.policy.Resource;
import com.amazonaws.auth.policy.Statement;
import com.amazonaws.auth.policy.actions.S3Actions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.AmazonS3Exception;
import com.amazonaws.services.s3.model.Bucket;
import com.amazonaws.services.s3.model.BucketPolicy;
import com.amazonaws.services.s3.model.DeleteObjectsRequest;
import com.amazonaws.services.s3.model.ListObjectsV2Result;
import com.amazonaws.services.s3.model.ListVersionsRequest;
import com.amazonaws.services.s3.model.ObjectListing;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectInputStream;
import com.amazonaws.services.s3.model.S3ObjectSummary;
import com.amazonaws.services.s3.model.S3VersionSummary;
import com.amazonaws.services.s3.model.VersionListing;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Iterator;
import java.util.List;

/**
 * <p>
 *
 * </p>
 *
 * @author Shik
 * @project springboot
 * @package com.zkgengkun.support.s3
 * @since 2019-01-02
 */
@Component
public class S3Helper {

    @Autowired
    private AmazonS3 s3;

    public OperationsForBucket operationsForBucket;
    public OperationsForObject operationsForObject;
    public OperationsForBucketPolicy operationsForBucketPolicy;

    S3Helper() {
        operationsForBucket = new OperationsForBucket();
        operationsForObject = new OperationsForObject();
        operationsForBucketPolicy = new OperationsForBucketPolicy();
    }

    public class OperationsForBucket {
        public Bucket getBucket(String bucket_name) {
            Bucket named_bucket = null;
            List<Bucket> buckets = s3.listBuckets();
            for (Bucket b : buckets) {
                if (b.getName().equals(bucket_name)) {
                    named_bucket = b;
                }
            }
            return named_bucket;
        }

        public Bucket createBucket(String bucket_name) {
            Bucket b = null;
            if (s3.doesBucketExistV2(bucket_name)) {
                System.out.format("Bucket %s already exists.\n", bucket_name);
                b = getBucket(bucket_name);
            } else {
                try {
                    b = s3.createBucket(bucket_name);
                } catch (AmazonS3Exception e) {
                    System.err.println(e.getErrorMessage());
                }
            }
            String policy_text = operationsForBucketPolicy.getBucketPolict(bucket_name);
            if(StringUtils.isBlank(policy_text)) {
                operationsForBucketPolicy.setPublicReadPolicy(bucket_name);
            }
            return b;
        }

        public void deleteBucket(String bucket_name) {
            System.out.println("Deleting S3 bucket: " + bucket_name);

            try {
                System.out.println(" - removing objects from bucket");
                ObjectListing object_listing = s3.listObjects(bucket_name);
                while (true) {
                    for (Iterator<?> iterator =
                         object_listing.getObjectSummaries().iterator();
                         iterator.hasNext(); ) {
                        S3ObjectSummary summary = (S3ObjectSummary) iterator.next();
                        s3.deleteObject(bucket_name, summary.getKey());
                    }

                    // more object_listing to retrieve?
                    if (object_listing.isTruncated()) {
                        object_listing = s3.listNextBatchOfObjects(object_listing);
                    } else {
                        break;
                    }
                }
                ;

                System.out.println(" - removing versions from bucket");
                VersionListing version_listing = s3.listVersions(
                        new ListVersionsRequest().withBucketName(bucket_name));
                while (true) {
                    for (Iterator<?> iterator =
                         version_listing.getVersionSummaries().iterator();
                         iterator.hasNext(); ) {
                        S3VersionSummary vs = (S3VersionSummary) iterator.next();
                        s3.deleteVersion(
                                bucket_name, vs.getKey(), vs.getVersionId());
                    }

                    if (version_listing.isTruncated()) {
                        version_listing = s3.listNextBatchOfVersions(
                                version_listing);
                    } else {
                        break;
                    }
                }

                System.out.println(" OK, bucket ready to delete!");
                s3.deleteBucket(bucket_name);
            } catch (AmazonServiceException e) {
                System.err.println(e.getErrorMessage());
            }
            System.out.println("Done!");
        }

        public List<Bucket> listBuckets() {
            List<Bucket> buckets = s3.listBuckets();
            System.out.println("Your Amazon S3 buckets are:");
            for (Bucket b : buckets) {
                System.out.println("* " + b.getName());
            }
            return buckets;
        }
    }

    public class OperationsForObject {
        public String putObject(String bucket_name, String file_path) {
            String key_name = Paths.get(file_path).getFileName().toString();

            System.out.format("Uploading %s to S3 bucket %s...\n", file_path, bucket_name);
            try {
                s3.putObject(bucket_name, key_name, new File(file_path));
            } catch (AmazonServiceException e) {
                System.err.println(e.getErrorMessage());
            }
            System.out.println("Done!");
            return key_name;
        }

        public void deleteObject(String bucket_name, String key_name) {

            System.out.format("Deleting object %s from S3 bucket: %s\n", key_name,
                    bucket_name);
            try {
                s3.deleteObject(bucket_name, key_name);
            } catch (AmazonServiceException e) {
                System.err.println(e.getErrorMessage());
            }
            System.out.println("Done!");
        }

        public void deleteObjects(String bucket_name, String[] object_keys) {

            System.out.println("Deleting objects from S3 bucket: " + bucket_name);
            for (String k : object_keys) {
                System.out.println(" * " + k);
            }

            try {
                DeleteObjectsRequest dor = new DeleteObjectsRequest(bucket_name).withKeys(object_keys);
                s3.deleteObjects(dor);
            } catch (AmazonServiceException e) {
                System.err.println(e.getErrorMessage());
            }
            System.out.println("Done!");
        }

        public void getObject(String bucket_name, String key_name, String file_path) {
            System.out.format("Downloading %s from S3 bucket %s...\n", key_name, bucket_name);
            try {
                S3Object o = s3.getObject(bucket_name, key_name);
                S3ObjectInputStream s3is = o.getObjectContent();
                FileOutputStream fos = new FileOutputStream(new File(file_path));
                byte[] read_buf = new byte[1024];
                int read_len = 0;
                while ((read_len = s3is.read(read_buf)) > 0) {
                    fos.write(read_buf, 0, read_len);
                }
                s3is.close();
                fos.close();
            } catch (AmazonServiceException e) {
                System.err.println(e.getErrorMessage());
            } catch (FileNotFoundException e) {
                System.err.println(e.getMessage());
            } catch (IOException e) {
                System.err.println(e.getMessage());
            }
            System.out.println("Done!");
        }

        public void listObjects(String bucket_name) {
            System.out.format("Objects in S3 bucket %s:\n", bucket_name);
            ListObjectsV2Result result = s3.listObjectsV2(bucket_name);
            List<S3ObjectSummary> objects = result.getObjectSummaries();
            for (S3ObjectSummary os : objects) {
                System.out.println("* " + os.getKey());
            }
        }

        public void copyObjectToBucket(String object_key, String from_bucket, String to_bucket) {

            System.out.format("Copying object %s from bucket %s to %s\n",
                    object_key, from_bucket, to_bucket);
            try {
                s3.copyObject(from_bucket, object_key, to_bucket, object_key);
            } catch (AmazonServiceException e) {
                System.err.println(e.getErrorMessage());
                System.exit(1);
            }
            System.out.println("Done!");
        }
    }

    public class OperationsForBucketPolicy {
        public String getBucketPolict(String bucket_name) {
            String policy_text = null;
            System.out.format("Getting policy for bucket: \"%s\"\n\n", bucket_name);
            BucketPolicy bucket_policy = null;
            try {
                bucket_policy = s3.getBucketPolicy(bucket_name);
                policy_text = bucket_policy.getPolicyText();
            } catch (AmazonServiceException e) {
                System.err.println(e.getErrorMessage());
            }

            if (policy_text == null) {
                System.out.println("The specified bucket has no bucket policy.");
            } else {
                System.out.println("Returned policy:");
                System.out.println("----");
                System.out.println(policy_text);
                System.out.println("----\n");
            }

            System.out.println("Done!");
            return policy_text;
        }

        public void setPublicReadPolicy(String bucket_name) {
            String policy_text = getPublicReadPolicy(bucket_name);;

            System.out.println("Setting policy:");
            System.out.println("----");
            System.out.println(policy_text);
            System.out.println("----");
            System.out.format("On S3 bucket: \"%s\"\n", bucket_name);

            setBucketPolicy(bucket_name, policy_text);

            System.out.println("Done!");
        }

        public void deleteBucketPolicy(String bucket_name) {
            System.out.format("Deleting policy from bucket: \"%s\"\n\n", bucket_name);

            try {
                s3.deleteBucketPolicy(bucket_name);
            } catch (AmazonServiceException e) {
                System.err.println(e.getErrorMessage());
            }

            System.out.println("Done!");
        }

        private String getPublicReadPolicy(String bucket_name) {
            Policy bucket_policy = new Policy().withStatements(
                    new Statement(Statement.Effect.Allow)
                            .withPrincipals(Principal.AllUsers)
                            .withActions(S3Actions.GetObject)
                            .withResources(new Resource(
                                    "arn:aws:s3:::" + bucket_name + "/*")));
            return bucket_policy.toJson();
        }

        private void setBucketPolicy(String bucket_name, String policy_text) {
            try {
                s3.setBucketPolicy(bucket_name, policy_text);
            } catch (AmazonServiceException e) {
                System.err.println(e.getErrorMessage());
            }
        }
    }
}
